import 'dart:convert';

import 'package:bot_toast/bot_toast.dart';
import 'package:dio/dio.dart';
import 'package:flutter_riverpod_demo/utils/consts/error_code.dart';
import 'package:flutter_riverpod_demo/utils/consts/server_url.dart';
import 'package:flutter_riverpod_demo/utils/events/auth_event.dart';
import 'package:flutter_riverpod_demo/utils/models/dio_result.dart';
import 'package:sp_util/sp_util.dart';

import 'consts/app_config.dart';
import 'events/event_bus.dart';
import 'models/dio_page_result.dart';

class Http {
  Http._();
  static final Http _instance = Http._();
  factory Http() => _instance;

  final Dio _dio = Dio(
    BaseOptions(
      baseUrl: ServerUrl.BASE_URL,
      connectTimeout: const Duration(seconds: 10),
      contentType: Headers.jsonContentType,
    ),
  )
    ..interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) {
          /// 添加认证信息
          options.headers["Authorization"] =
              "Basic ${base64Encode(utf8.encode("${AppConfig.clientId}:${AppConfig.clientSecret}"))}";

          /// 如果Token存在，则将其添加到请求头中
          String? token = SpUtil.getString('accessToken');
          if (token != null && token.isNotEmpty) {
            options.headers["Blade-Auth"] = "bearer $token";
          }
          return handler.next(options);
        },
        onResponse: (response, handler) {
          if (response.requestOptions.responseType == ResponseType.json) {
            final responseData = response.data as Map<String, dynamic>;
            final code = responseData.containsKey("code")
                ? (responseData["code"] as int)
                : 200;
            final success = responseData.containsKey("success")
                ? (responseData["success"] as bool)
                : true;
            final msg = responseData.containsKey("msg")
                ? (responseData["msg"] as String)
                : ErrorCode.fromCode(code).message;
            if (code == 401) {
              SpUtil.clear();
              BotToast.showText(text: msg);
              eventBus.fire(AuthEvent(true));
              return handler.reject(DioException(
                requestOptions: response.requestOptions,
                message: msg,
              ));
            } else if (code != 200) {
              BotToast.showText(text: msg);
              // return handler.reject(DioException(
              //   requestOptions: response.requestOptions,
              //   message: msg,
              // ));
              return handler.next(Response(
                requestOptions: response.requestOptions,
                data: DioResult(
                  code: code,
                  success: success,
                  msg: msg,
                  data: null,
                ),
                statusCode: response.statusCode,
                statusMessage: response.statusMessage,
              ));
            } else {
              final resultData = responseData["data"] as Map<String, dynamic>;
              if (resultData.containsKey("total") &&
                  resultData.containsKey("current") &&
                  resultData.containsKey("size") &&
                  resultData.containsKey("pages")) {
                final pageData = PageData(
                  records: resultData['records'],
                  total: resultData['total'] as int,
                  size: resultData['size'] as int,
                  current: resultData['current'] as int,
                  orders: resultData['orders'],
                  optimizeCountSql: resultData['optimizeCountSql'] as bool,
                  searchCount: resultData['searchCount'] as bool,
                  countId: resultData['countId'] as String,
                  maxLimit: resultData['maxLimit'],
                  pages: resultData['pages'] as int,
                );
                return handler.resolve(Response(
                  requestOptions: response.requestOptions,
                  data: DioPageResult(
                    code: code,
                    success: success,
                    msg: msg,
                    data: pageData,
                  ),
                  statusCode: response.statusCode,
                  statusMessage: response.statusMessage,
                ));
              } else {
                return handler.resolve(Response(
                  requestOptions: response.requestOptions,
                  data: DioResult(
                    code: code,
                    success: success,
                    msg: msg,
                    data: responseData["data"],
                  ),
                  statusCode: response.statusCode,
                  statusMessage: response.statusMessage,
                ));
              }
            }
          } else {
            /// 非json响应数据直接返回
            // ResponseType.bytes // List<int>
            // ResponseType.plain // String
            // ResponseType.bytes // List<int>
            return handler.resolve(response);
          }
        },
        onError: (error, handler) async {
          // 检查错误类型
          String errorMessage;
          switch (error.type) {
            case DioExceptionType.connectionTimeout:
              // 处理连接超时错误
              errorMessage = "连接超时，请检查网络连接是否正常";
              break;
            case DioExceptionType.sendTimeout:
              // 处理发送超时错误
              errorMessage = "发送超时，请检查网络连接是否正常";
              break;
            case DioExceptionType.receiveTimeout:
              // 处理接收超时错误
              errorMessage = "接收超时，请检查网络连接是否正常";
              break;
            case DioExceptionType.badCertificate:
              // 处理证书不被信任错误
              errorMessage = "证书不被信任，请检查证书是否正确";
              break;
            case DioExceptionType.badResponse:
              // 处理不正确状态码错误，如404、500等
              errorMessage = "服务器返回了不正确的状态码，请检查请求参数是否正确";
              break;
            case DioExceptionType.cancel:
              // 处理连接被取消错误
              errorMessage = "请求被取消，请检查网络是否正常";
              break;
            case DioExceptionType.connectionError:
              // 处理网络连接错误
              errorMessage = "网络连接错误，请检查网络是否正常";
              break;
            case DioExceptionType.unknown:
              // 处理未知类型错误
              errorMessage = "未知错误，请检查网络是否正常";
              break;
          }
          if (error.response is Response<dynamic>) {
            final responseData = error.response?.data as Map<String, dynamic>;
            final code = responseData.containsKey("code")
                ? (responseData["code"] as int)
                : 200;
            final success = responseData.containsKey("success")
                ? (responseData["success"] as bool)
                : true;
            final msg = responseData.containsKey("msg")
                ? (responseData["msg"] as String)
                : error.message ?? errorMessage;
            BotToast.showText(text: msg);
            return handler.resolve(Response(
              requestOptions: error.response!.requestOptions,
              data: DioResult(
                code: code,
                success: success,
                msg: msg,
                data: null,
              ),
              statusCode: error.response!.statusCode,
              statusMessage: error.response!.statusMessage,
            ));
          } else {
            final dioException = DioException(
              requestOptions: error.requestOptions,
              message: error.message ?? errorMessage,
            );
            return handler.reject(dioException);
          }
        },
      ),
    )
    ..interceptors.add(
      LogInterceptor(
        requestBody: true,
        responseBody: true,
      ),
    );

  /// Convenience method to make an HTTP HEAD request.
  static Future<Response<T>> head<T>(
    String path, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
  }) =>
      _instance._dio.head<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );

  /// Convenience method to make an HTTP GET request.
  static Future<Response<T>> get<T>(
    String path, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onReceiveProgress,
  }) =>
      _instance._dio.get<T>(
        path,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onReceiveProgress: onReceiveProgress,
      );

  /// Convenience method to make an HTTP POST request.
  static Future<Response<T>> post<T>(
    String path, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) =>
      _instance._dio.post<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );

  /// Convenience method to make an HTTP PUT request.
  static Future<Response<T>> put<T>(
    String path, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) =>
      _instance._dio.put<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );

  /// Convenience method to make an HTTP PATCH request.
  static Future<Response<T>> patch<T>(
    String path, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) =>
      _instance._dio.patch<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );

  /// Convenience method to make an HTTP DELETE request.
  static Future<Response<T>> delete<T>(
    String path, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    Options? options,
    CancelToken? cancelToken,
  }) =>
      _instance._dio.delete<T>(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
        cancelToken: cancelToken,
      );

  /// {@template dio.Dio.download}
  /// Download the file and save it in local. The default http method is "GET",
  /// you can custom it by [Options.method].
  ///
  /// [urlPath] is the file url.
  ///
  /// The file will be saved to the path specified by [savePath].
  /// The following two types are accepted:
  /// 1. `String`: A path, eg "xs.jpg"
  /// 2. `FutureOr<String> Function(Headers headers)`, for example:
  ///    ```dart
  ///    await dio.download(
  ///      url,
  ///      (Headers headers) {
  ///        // Extra info: redirect counts
  ///        print(headers.value('redirects'));
  ///        // Extra info: real uri
  ///        print(headers.value('uri'));
  ///        // ...
  ///        return (await getTemporaryDirectory()).path + 'file_name';
  ///      },
  ///    );
  ///    ```
  ///
  /// [onReceiveProgress] is the callback to listen downloading progress.
  /// Please refer to [ProgressCallback].
  ///
  /// [deleteOnError] whether delete the file when error occurs.
  /// The default value is [true].
  ///
  /// [lengthHeader] : The real size of original file (not compressed).
  /// When file is compressed:
  /// 1. If this value is 'content-length', the `total` argument of
  ///    [onReceiveProgress] will be -1.
  /// 2. If this value is not 'content-length', maybe a custom header indicates
  ///    the original file size, the `total` argument of [onReceiveProgress]
  ///    will be this header value.
  ///
  /// You can also disable the compression by specifying the 'accept-encoding'
  /// header value as '*' to assure the value of `total` argument of
  /// [onReceiveProgress] is not -1. For example:
  ///
  /// ```dart
  /// await dio.download(
  ///   url,
  ///   (await getTemporaryDirectory()).path + 'flutter.svg',
  ///   options: Options(
  ///     headers: {HttpHeaders.acceptEncodingHeader: '*'}, // Disable gzip
  ///   ),
  ///   onReceiveProgress: (received, total) {
  ///     if (total <= 0) return;
  ///     print('percentage: ${(received / total * 100).toStringAsFixed(0)}%');
  ///   },
  /// );
  /// ```
  /// {@endtemplate}
  static Future<Response> download(
    String urlPath,
    dynamic savePath, {
    ProgressCallback? onReceiveProgress,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    bool deleteOnError = true,
    String lengthHeader = Headers.contentLengthHeader,
    Object? data,
    Options? options,
  }) =>
      _instance._dio.download(
        urlPath,
        savePath,
        onReceiveProgress: onReceiveProgress,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        deleteOnError: deleteOnError,
        lengthHeader: lengthHeader,
        data: data,
        options: options,
      );

  /// Make HTTP request with options.
  static Future<Response<T>> request<T>(
    String url, {
    Object? data,
    Map<String, dynamic>? queryParameters,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) =>
      _instance._dio.request<T>(
        url,
        data: data,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        options: options,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress,
      );
}
